import Utils (splitAll)
import Data.List
import Data.Char (isDigit)

type Passport =  [(String,String)]

parsePassport :: String -> Passport
parsePassport str = 
    map parseKeyPair keypairs
    where
        keypairs = splitAll "\n " str
    
        parseKeyPair :: String -> (String,String)
        parseKeyPair keypair = ((tokens!!0),(tokens!!1))
            where
                tokens = splitAll ":" keypair

parseInput :: String -> [Passport]
parseInput = map parsePassport . map unlines . splitAll [""] . lines

---------------------------------------------------------------------------

checkNumber :: (Int,Int) -> String -> Bool
checkNumber (least,most) string = 
    (number >= least) && (number <= most)
    where
        number = read string :: Int

checkBirthYear :: String -> Bool
checkBirthYear = checkNumber (1920,2002)

checkIssueYear :: String -> Bool
checkIssueYear = checkNumber (2010,2020)

checkExpirationYear :: String -> Bool
checkExpirationYear = checkNumber (2020,2030)

checkHeight :: String -> Bool
checkHeight string = 
    (length string > 2) &&
    case uint of
        "cm" -> checkNumber (150,193) numberString
        "in" -> checkNumber (59,76) numberString
        _ -> False
    where
        (numberString,uint) = span isDigit string
        number = read numberString :: Int

isHexChar :: Char -> Bool
isHexChar c = elem c ['0'..'9'] || elem c ['a'..'f']

checkHairColor :: String -> Bool
checkHairColor string = 
    (head string == '#') && 
    (length characters == 6) && 
    (all isHexChar characters)
    where
        characters = tail string

checkEyeColor :: String -> Bool
checkEyeColor string = string `elem` ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]

checkPassportID :: String -> Bool
checkPassportID string = 
    (length string == 9) &&
    (all isDigit string) 

checkInfo :: (String,String) -> Bool
checkInfo (key,value) = case key of
    "byr" -> checkBirthYear value
    "iyr" -> checkIssueYear value
    "eyr" -> checkExpirationYear value
    "hgt" -> checkHeight value
    "hcl" -> checkHairColor value
    "ecl" -> checkEyeColor value
    "pid" -> checkPassportID value
    "cid" -> True

isPassportValid :: Passport -> Bool
isPassportValid passport = 
    ((length passport == 8) || (length passport == 7 && withoutCID passport))
    && pass    
    where
        pass = all checkInfo passport
        withoutCID :: Passport -> Bool
        withoutCID = not . elem "cid" . map fst 

main = do
    input <- readFile "./input.txt"
    print . length . filter isPassportValid . parseInput $ input